package com.gitee.wsl.console


import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic


private class TextSurface(
    override val width: Int,
    override val height: Int,
) : TextCanvas {
    private val rowBuilders = Array(height) {
        StringBuilder(width).apply {
            repeat(width) {
                append(' ')
            }
        }
    }

    override fun write(row: Int, column: Int, char: Char) {
        val rowBuilder = rowBuilders[row]
        val writeIndex = rowBuilder.visualIndex(column)
        rowBuilder[writeIndex] = char
    }

    override fun write(row: Int, column: Int, string: String) {
        string.split('\n').forEachIndexed { lineIndex, line ->
            val rowBuilder = rowBuilders[row + lineIndex]
            val writeStartIndex = rowBuilder.visualIndex(column)
            val writeEndIndex = rowBuilder.visualIndex(column + line.visualWidth)

            rowBuilder.setRange(writeStartIndex, writeEndIndex, line)
        }
    }

    override fun toString(): String {
        if (height == 0) {
            return ""
        }
        val rowCharacterCount = rowBuilders.sumOf { it.length }
        val newLineCount = height - 1
        return buildString(rowCharacterCount + newLineCount) {
            rowBuilders.forEachIndexed { index, rowBuilder ->
                if (index > 0) {
                    append('\n')
                }
                append(rowBuilder)
            }
        }
    }
}

interface TextCanvas {
    val width: Int
    val height: Int

    fun write(row: Int, column: Int, char: Char)
    fun write(row: Int, column: Int, string: String)

    fun clip(left: Int, top: Int, right: Int, bottom: Int): TextCanvas {
        return ClippedTextCanvas(this, left, right, top, bottom)
    }

    override fun toString(): String

    companion object {
        @JvmStatic
        @JvmName("ofSize")
        operator fun invoke(width: Int, height: Int): TextCanvas {
            return TextSurface(width, height)
        }
    }
}

private class ClippedTextCanvas(
    private val canvas: TextCanvas,
    private val left: Int,
    right: Int,
    private val top: Int,
    bottom: Int,
) : TextCanvas {
    override val width = right - left
    override val height = bottom - top

    override fun write(row: Int, column: Int, char: Char) {
        canvas.write(top + row, left + column, char)
    }

    override fun write(row: Int, column: Int, string: String) {
        canvas.write(top + row, left + column, string)
    }

    override fun toString(): String {
        throw UnsupportedOperationException("Rendering a clipped canvas is not supported")
    }
}


private val ansiColorEscape = Regex("""\u001B\[\d+(;\d+)*m""")

fun CharSequence.visualIndex(index: Int): Int {
    var remaining = index
    forEachVisualCharacter {
        if (remaining == 0) {
            return it
        }
        remaining--
    }
    if (remaining == 0) {
        return length
    }
    throw IndexOutOfBoundsException()
}

val CharSequence.visualWidth: Int get() {
    var count = 0
    forEachVisualCharacter {
        count++
    }
    return count
}

private inline fun CharSequence.forEachVisualCharacter(block: (index: Int) -> Unit) {
    var index = 0

    // These values will force a code path that searches for the first real match below.
    var nextMatchStart = 0
    var nextMatchEnd = -1

    val length = length
    while (index < length) {
        if (index == nextMatchStart) {
            // Jump over ANSI control sequence.
            index = nextMatchEnd + 1

            // Find the next ANSI control sequence, if any.
            val match = ansiColorEscape.find(this, index)
            if (match != null) {
                nextMatchStart = match.range.first
                nextMatchEnd = match.range.last
            } else {
                // No future matches. Ensure we never take this conditional again.
                nextMatchStart = length
            }

            // Restart loop since there may be successive ANSI sequences.
            continue
        }

        block(index)

        val code = this[index].code
        index++

        // Check for a surrogate pair which render as a single visual glyph.
        if (code.isHighSurrogate() && index < length && this[index].code.isLowSurrogate()) {
            index++
        }

        // Consume combining diacritics which render on the preceding code point.
        while (index < length && this[index].code.isCombiningDiacritical()) {
            index++
        }
    }
}

@Suppress("NOTHING_TO_INLINE")
private inline fun Int.isLowSurrogate(): Boolean = this in 0xDC00..0xDFFF
@Suppress("NOTHING_TO_INLINE")
private inline fun Int.isHighSurrogate(): Boolean = this in 0xD800..0xDBFF
@Suppress("NOTHING_TO_INLINE")
private inline fun Int.isCombiningDiacritical(): Boolean = this in 0x0300..0x036F
